﻿namespace org.webguvenligi.NSecureImageLib.GraphicFileFormats.Utilities
{
    #region Imported Libraries
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.IO.Compression;
    using System.Linq;
    using System.Text;
    using org.webguvenligi.NSecureImageLib.GraphicFileFormats.PNG;
    #endregion

    public static class Extenders
    {
        public static BinaryWriter CreateWriter(this MemoryStream currentStream)
        {
            return new BinaryWriter(currentStream);
        }
        /// <summary>
        /// Finds the chunks.
        /// </summary>
        /// <param name="foundChunks">The found chunks.</param>
        /// <param name="chunkType">Type of the chunk.</param>
        /// <returns></returns>
        public static IEnumerable<PNGBaseChunk> FindChunks(List<PNGBaseChunk> foundChunks,
                                             string chunkType)
        {
            var textChunks = from p in foundChunks
                             where p.ChunkTypeValue.ChunkTypeDescription == chunkType
                             select p;
            return textChunks;
        }
        /// <summary>
        /// Finds the chunk.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="foundChunks">The found chunks.</param>
        /// <returns></returns>
        public static T FindChunk<T>(this List<PNGBaseChunk> foundChunks) where T : PNGBaseChunk
        {
            var textChunks = (T)(from p in foundChunks
                                 where p.GetType().IsAssignableFrom(typeof(T))
                                 select p).SingleOrDefault();
            return textChunks;

        }
        /// <summary>
        /// Finds the chunk.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="foundChunks">The found chunks.</param>
        /// <param name="chunkIndex">Index of the chunk.</param>
        /// <returns></returns>
        public static T FindChunk<T>(this List<PNGBaseChunk> foundChunks, out int chunkIndex) where T : PNGBaseChunk
        {
            chunkIndex = -1;
            for (int i = 0; i < foundChunks.Count; i++)
            {
                T foundChunk = foundChunks[i] as T;
                if (foundChunk != null)
                {
                    chunkIndex = i;
                    return foundChunk;
                }
            }
            return null;
        }
        public static IEnumerable<PNGBaseChunk> FindChunks<T>(this IList<PNGBaseChunk> foundChunks) where T : PNGBaseChunk
        {
            return FindChunks(foundChunks, typeof(T));
        }
        /// <summary>
        /// Finds the chunks.
        /// </summary>
        /// <param name="foundChunks">The found chunks.</param>
        /// <param name="chunkType">Type of the chunk.</param>
        /// <returns></returns>
        public static IEnumerable<PNGBaseChunk> FindChunks(this IList<PNGBaseChunk> foundChunks,
                                                     Type chunkType)
        {
            var textChunks = (from p in foundChunks
                              where p.GetType().IsAssignableFrom(chunkType)
                              select p);
            return textChunks;
        }
        /// <summary>
        /// Checks the total length.
        /// </summary>
        /// <param name="sourceReader">The source reader.</param>
        /// <param name="byteCount">The byte count.</param>
        public static void CheckTotalLengthLessOrEqual(this BinaryReader sourceReader, long byteCount)
        {
            if (BytesRemaining(sourceReader) <= byteCount)
                throw new InvalidDataException("Invalid Stream Length");
        }
        public static double ReadFloatingPoint(this BinaryReader sourceReader)
        {
            string numberToBeParsed = sourceReader.ReadNullDelimitedString();
            int ePosition = Math.Max(numberToBeParsed.IndexOf('e'),
                             numberToBeParsed.IndexOf('E'));
            string targetParsingNumber = numberToBeParsed.Substring(0, (ePosition < 0 ? numberToBeParsed.Length : ePosition));
            double doublePart = Double.Parse(targetParsingNumber);
            if (ePosition >= 0)
                doublePart *= Math.Pow(10d, Double.Parse(numberToBeParsed.Substring(ePosition + 1)));
            return doublePart;
        }

        /// <summary>
        /// Reads to null.
        /// </summary>
        /// <param name="sourceReader">The source reader.</param>
        /// <returns></returns>
        public static String ReadNullDelimitedString(this BinaryReader sourceReader)
        {
            return ReadToNull(sourceReader, sourceReader.BytesRemaining());
        }

        /// <summary>
        /// Reads the bytes.
        /// </summary>
        /// <param name="sourceReader">The source reader.</param>
        /// <returns></returns>
        public static byte[] ReadBytes(this BinaryReader sourceReader)
        {
            List<byte> remainingBytes = new List<byte>();
            while (Int32.MaxValue < sourceReader.BytesRemaining())
            {
                remainingBytes.AddRange(sourceReader.ReadBytes(Int32.MaxValue));
            }
            if (sourceReader.BytesRemaining() > 0)
                remainingBytes.AddRange(sourceReader.ReadBytes(Convert.ToInt32(sourceReader.BytesRemaining())));
            return remainingBytes.ToArray();
        }
        /// <summary>
        /// Reads to null UT f8.
        /// </summary>
        /// <param name="sourceReader">The source reader.</param>
        /// <returns></returns>
        public static String ReadToNullUTF8(this BinaryReader sourceReader)
        {
            return ReadToNullUTF8(sourceReader, sourceReader.BytesRemaining());
        }
        /// <summary>
        /// Reads to null UT f8.
        /// </summary>
        /// <param name="sourceReader">The source reader.</param>
        /// <param name="maxLength">Length of the max.</param>
        /// <returns></returns>
        public static String ReadToNullUTF8(this BinaryReader sourceReader, long maxLength)
        {
            byte currentByte;
            using (MemoryStream dataStream = new MemoryStream())
            {
                while (((currentByte = sourceReader.ReadByte()) != 0)
                   && (dataStream.Length <= maxLength))
                {
                    dataStream.WriteByte(currentByte);
                }
                return Encoding.UTF8.GetString(dataStream.ToArray());
            }
        }
        /// <summary>
        /// Decompresses the string.
        /// </summary>
        /// <param name="compressed">The compressed.</param>
        /// <returns></returns>
        public static string DecompressString(this byte[] compressed)
        {
            string outputString = null;
            using (MemoryStream inStream = new MemoryStream(compressed))
            {
                inStream.Seek(0, SeekOrigin.Begin);
                using (DeflateStream decompressStream = new DeflateStream(inStream, CompressionMode.Decompress))
                {
                    using (StreamReader sr = new StreamReader(decompressStream))
                    {
                        outputString = sr.ReadToEnd();
                    }
                }
            }
            return outputString;
        }
        private readonly static System.Text.Encoding Latin1Encoding =
        Encoding.GetEncoding(28591);
        /// <summary>
        /// Compresses the string latin1.
        /// </summary>
        /// <param name="inputString">The input string.</param>
        /// <returns></returns>
        public static byte[] CompressStringLatin1(this string inputString)
        {
            byte[] output = null;
            using (MemoryStream outStream = new MemoryStream())
            {
                using (DeflateStream compressStream = new DeflateStream(outStream, CompressionMode.Compress, true))
                {
                    using (StreamWriter strwr = new StreamWriter(compressStream, Latin1Encoding))
                    {
                        strwr.Write(inputString);
                        strwr.Flush();
                    }
                }
                output = outStream.ToArray();
            }
            return output;
        }
        /// <summary>
        /// Compresses the string.
        /// </summary>
        /// <param name="inputString">The input string.</param>
        /// <returns></returns>
        public static byte[] CompressStringUTF8(this string inputString)
        {
            byte[] output = null;
            using (MemoryStream outStream = new MemoryStream())
            {
                using (DeflateStream compressStream = new DeflateStream(outStream, CompressionMode.Compress, true))
                {
                    using (StreamWriter strwr = new StreamWriter(compressStream, UTF8Encoding.UTF8))
                    {
                        strwr.Write(inputString);
                        strwr.Flush();
                    }
                }
                output = outStream.ToArray();
            }
            return output;
        }
        /// <summary>
        /// Compresses the byte array.
        /// </summary>
        /// <param name="inputArray">The input array.</param>
        /// <returns></returns>
        public static byte[] CompressByteArray(this byte[] inputArray)
        {
            byte[] output = null;
            if (inputArray != null && inputArray.Length > 0)
            {
                using (MemoryStream outStream = new MemoryStream())
                {
                    using (DeflateStream compressStream = new DeflateStream(outStream, CompressionMode.Compress))
                    {
                        compressStream.Write(inputArray, 0, inputArray.Length);
                        compressStream.Flush();
                        compressStream.Close();
                        output = outStream.ToArray();
                    }
                }
            }
            else
            {
                output = inputArray;
            }
            return output;
        }
        /// <summary>
        /// Decompresses the byte array to stream.
        /// </summary>
        /// <param name="inputArray">The input array.</param>
        /// <returns></returns>
        public static Stream DecompressByteArrayToStream(this byte[] inputArray)
        {
            MemoryStream currentStream =
                new MemoryStream(DecompressByteArray(inputArray));
            currentStream.Position = 0;
            return currentStream;
        }
        /// <summary>
        /// Decompresses the byte array.
        /// </summary>
        /// <param name="inputArray">The input array.</param>
        /// <returns></returns>
        public static byte[] DecompressByteArray(this byte[] inputArray)
        {
            byte[] targetValue;
            if (inputArray != null && inputArray.Length > 0)
            {
                using (MemoryStream resultStream = new MemoryStream())
                {
                    using (MemoryStream compressedStream = new MemoryStream(inputArray))
                    {
                        compressedStream.Position = 0;
                        using (DeflateStream compressedArray = new DeflateStream(compressedStream, CompressionMode.Decompress))
                        {
                            byte[] buffer = new byte[BufferSize()];
                            int numRead = 0;
                            while ((numRead = compressedArray.Read(buffer, 0, BufferSize())) != 0)
                            {
                                resultStream.Write(buffer, 0, numRead);
                                resultStream.Flush();
                            }
                            targetValue = resultStream.ToArray();
                        }
                    }
                }
            }
            else
            {
                targetValue = inputArray;
            }
            return targetValue;
        }

        /// <summary>
        /// Buffers the size.
        /// </summary>
        /// <returns></returns>
        private static int BufferSize()
        {
            return 4096;
        }
        /// <summary>
        /// Reads to null.
        /// </summary>
        /// <param name="sourceReader">The source reader.</param>
        /// <param name="maxLength">Length of the max.</param>
        /// <returns></returns>
        public static String ReadToNull(this BinaryReader sourceReader, long maxLength)
        {
            //TODO Fix The Max Length Check When No Terminator (0) Encountered.
            StringBuilder resultBuilder = new StringBuilder();
            byte currentByte;
            while (((currentByte = sourceReader.ReadByte()) != 0)
                   && (resultBuilder.Length <= maxLength))
                resultBuilder.Append((char)currentByte);
            if (currentByte != 0)
                throw new PNG.Exceptions.PNGBaseException();
            return resultBuilder.ToString();
        }
        public static void WriteNullDelimitedString(this BinaryWriter sourceWriter, string sourceString)
        {
            if (String.IsNullOrEmpty(sourceString))
            {
                sourceWriter.Write((byte)0);
            }
            else
            {

            }
        }

        /// <summary>
        /// Merges Byte Array
        /// </summary>
        /// <param name="owner">The Owner Type Of The Extender Method</param>
        /// <param name="sourceArray">The Byte Array Which Will Be Merged With Owner Array</param>
        /// <returns>Merged Bytes Array</returns>
        /// <exception cref="System.ArgumentNullException"></exception>
        public static byte[] MergeByteArray(this byte[] owner, byte[] sourceArray)
        {

            if (sourceArray == null)
            {
                throw new ArgumentNullException("sourceArray", "sourceArray cannot be null");
            }

            var result = from own in owner
                         from owners in sourceArray
                         select own;

            return result.ToArray();

        }

        public static bool GetBit(this byte b, byte position)
        {
            return ((b & (byte)(1 << position)) != 0);
        }
        /// <summary>
        /// Sets The Bit In The Given Byte
        /// </summary>
        /// <param name="b"></param>
        /// <param name="position"></param>
        /// <param name="newBitValue"></param>
        /// <returns></returns>
        public static byte SetBit(this byte b, byte position, bool newBitValue)
        {
            byte mask = (byte)(1 << position);
            if (newBitValue)
            {
                return (byte)(b | mask);
            }
            else
            {
                return (byte)(b & ~mask);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="thisBytes"></param>
        /// <param name="sourceBytes"></param>
        /// <example>(codes 65-90 and 97-122);</example>
        /// <returns></returns>
        public static bool IsInRange(this byte[] thisBytes,
                                     params Range[] totalRange)
        {
            List<int> rangeList = new List<int>();
            foreach (Range tempRange in totalRange)
            {
                for (int x = tempRange.BeginRange; x <= tempRange.EndRange; x++)
                {
                    rangeList.Add(x);
                }
            }

            var elements = from range in thisBytes
                           where (!rangeList.Contains(range))
                           select range;

            return (elements.Count() == 0);

        }

        public static bool IsAscii(this byte[] thisBytes)
        {
            return IsInRange(thisBytes, new Range() { BeginRange = 65, EndRange = 90 }, new Range() { BeginRange = 97, EndRange = 122 });
        }

        public static bool StartsWith(this byte[] thisBytes, byte[] thatBytes)
        {
            for (int i = 0; i < thatBytes.Length; i += 1)
            {
                if (thisBytes[i] != thatBytes[i])
                {
                    return false;
                }
            }
            return true;
        }

        public static long ReadLittleEndianInt64(this BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(long)];
            for (int i = 0; i < sizeof(long); i += 1)
            {
                bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToInt64(bytes, 0);
        }

        public static short ReadLittleEndianInt16(this BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(short)];
            for (int i = 0; i < sizeof(short); i += 1)
            {
                bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToInt16(bytes, 0);
        }

        public static ushort ReadLittleEndianUInt16(this BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(short)];
            for (int i = 0; i < sizeof(short); i += 1)
            {
                bytes[sizeof(short) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToUInt16(bytes, 0);
        }

        public static uint ReadLittleEndianUInt32(this BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(int)];
            for (int i = 0; i < sizeof(int); i += 1)
            {
                bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToUInt32(bytes, 0);
        }
        public static int ReadLittleEndianInt32(this BinaryReader binaryReader)
        {
            byte[] bytes = new byte[sizeof(int)];
            for (int i = 0; i < sizeof(int); i += 1)
            {
                bytes[sizeof(int) - 1 - i] = binaryReader.ReadByte();
            }
            return BitConverter.ToInt32(bytes, 0);
        }
        public static void WriteLittleEndian(this BinaryWriter binaryWriter, uint value)
        {
            binaryWriter.Write(BitConverter.GetBytes(value).Reverse().ToArray());
        }
        public static void WriteLittleEndian(this BinaryWriter binaryWriter, int value)
        {
            binaryWriter.Write(BitConverter.GetBytes(value).Reverse().ToArray());
        }
        public static void WriteLittleEndian(this BinaryWriter binaryWriter, short value)
        {
            binaryWriter.Write(BitConverter.GetBytes(value).Reverse().ToArray());
        }
        public static void WriteLittleEndian(this BinaryWriter binaryWriter, ushort value)
        {
            binaryWriter.Write(BitConverter.GetBytes(value).Reverse().ToArray());
        }
        public static void WriteLittleEndian(this BinaryWriter binaryWriter, long value)
        {
            binaryWriter.Write(BitConverter.GetBytes(value).Reverse().ToArray());
        }
        public static void WriteLittleEndian(this BinaryWriter binaryWriter, ulong value)
        {
            binaryWriter.Write(BitConverter.GetBytes(value).Reverse().ToArray());
        }
        public static bool CompareByteArrayPeeking(this Stream sourceStream, byte[] destinationArray)
        {
            byte[] sourceArray =
                new byte[destinationArray.Length];
            long initialPosition =
                sourceStream.Position;
            try
            {
                if ((sourceStream.CanRead) & ((sourceStream.Position + destinationArray.Length)
                                               + 1 < sourceStream.Length))
                {
                    sourceStream.Read(sourceArray, 0, sourceArray.Length);
                    return sourceArray.CompareByteArray(destinationArray);
                }
            }
            finally
            {
                sourceStream.Position = initialPosition;
            }
            return false;
        }

        /// <summary>
        /// Checks The Remaining Bytes To Be Read In The Stream
        /// </summary>
        /// <param name="sourceStream">The Stream To Be Checked</param>
        /// <returns>The Remaining Bytes In Stream</returns>
        public static long BytesRemaining(this Stream sourceStream)
        {
            return (sourceStream.Length - sourceStream.Position);
        }

        /// <summary>
        /// Ensures That The Given BinaryReader Contains Enough Bytes To Be Readed
        /// </summary>
        /// <param name="sourceReader">The BinaryReader To Be Checked</param>
        /// <param name="byteCount">The Total Number Of Byte That Must Be Remaining In The Current Stream</param>
        /// <exception cref="System.IO.InvalidDataException"></exception>
        public static void EnsureBytes(this BinaryReader sourceReader, long byteCount)
        {
            if (!(BytesRemaining(sourceReader) >= byteCount))
                throw new InvalidDataException("Invalid Stream Length");
        }

        public static byte[] ReadBytesEnsure(this BinaryReader sourceReader, int byteCount)
        {
            EnsureBytes(sourceReader, byteCount);
            return sourceReader.ReadBytes(byteCount);
        }

        public static void CheckTotalLength(this BinaryReader sourceReader, long byteCount)
        {
            if (BytesRemaining(sourceReader) != byteCount)
                throw new InvalidDataException("Invalid Stream Length");
        }
        /// <summary>
        /// Reads the bytes to end.
        /// </summary>
        /// <param name="sourceReader">The source reader.</param>
        /// <returns></returns>
        public static byte[] ReadBytesToEnd(this BinaryReader sourceReader)
        {
            using (MemoryStream targetStream = new MemoryStream())
            {
                while (sourceReader.BytesRemaining() > 0)
                    targetStream.WriteByte(sourceReader.ReadByte());
                return targetStream.ToArray();
            }
        }
        /// <summary>
        /// Checks The Remaining Bytes To Be Read In The BinaryReader
        /// </summary>
        /// <param name="sourceStream">The BinaryReader To Be Checked</param>
        /// <returns>The Remaining Bytes In Stream</returns>
        public static long BytesRemaining(this BinaryReader sourceReader)
        {
            return sourceReader.BaseStream.BytesRemaining();
        }


        public static bool CompareByteArray(this Stream sourceStream, byte[] destinationArray)
        {
            byte[] sourceArray =
                new byte[destinationArray.Length];
            if ((sourceStream.CanRead) && ((sourceStream.Position + destinationArray.Length)
                + 1 < sourceStream.Length))
            {
                sourceStream.Read(sourceArray, 0, sourceArray.Length);
                return sourceArray.CompareByteArray(destinationArray);
            }
            return false;
        }
        public static bool CompareByteArray(this byte[] sourceArray,
                                            byte[] destinationArray)
        {
            if (destinationArray == null)
                return false;

            if (sourceArray.Length != destinationArray.Length)
                return false;

            bool isEqual = true;

            for (int i = 0; i < sourceArray.Length; i++)
            {
                if (sourceArray[i] != destinationArray[i])
                {
                    isEqual = false;
                    break;
                }
            }

            return isEqual;

        }
    }
    public struct Range
    {
        public int BeginRange;
        public int EndRange;
    }
}